package store.lunangangster.engine.impl.entity.mgr;

import java.util.List;
import store.lunangangster.engine.NodeFlowException;
import store.lunangangster.engine.ProcessEngineConfiguration;
import store.lunangangster.engine.impl.entity.DeploymentEntity;
import store.lunangangster.engine.impl.entity.ExecutionEntity;
import store.lunangangster.engine.impl.entity.ProcessDefinitionEntity;
import store.lunangangster.engine.impl.entity.ResourceEntity;
import store.lunangangster.engine.impl.repository.BpmnDeployer;
import store.lunangangster.engine.impl.repository.DeployCacheManager;
import store.lunangangster.engine.impl.repository.ProcessDefinitionCacheEntry;
import store.lunangangster.engine.repository.Deployment;
import store.lunangangster.engine.repository.ProcessDefinition;

public class DeploymentManager {

  protected ProcessEngineConfiguration processEngineConfiguration;
  protected DeploymentEntityManager deploymentEntityManager;
  protected ProcessDefinitionEntityManager processDefinitionEntityManager;
  protected ResourceEntityManager resourceEntityManager;
  protected ExecutionEntityManager executionEntityManager;
  protected BpmnDeployer bpmnDeployer;
  protected DeployCacheManager deployCacheManager;


  public DeploymentManager(ProcessEngineConfiguration processEngineConfiguration) {
    this.processEngineConfiguration = processEngineConfiguration;
    this.deploymentEntityManager = processEngineConfiguration.getDeploymentEntityManager();
    this.processDefinitionEntityManager = processEngineConfiguration.getProcessDefinitionEntityManager();
    this.resourceEntityManager = processEngineConfiguration.getResourceEntityManager();
    this.executionEntityManager = processEngineConfiguration.getExecutionEntityManager();
    this.deployCacheManager = processEngineConfiguration.getDeployCacheManager();
    this.bpmnDeployer = processEngineConfiguration.getBpmnDeployer();
  }

  /**
   * 该方法会解析DeploymentEntity并且将与其相关的对象（如BpmnModel、ProcessDefinition）重新加载到cache中。
   * 如果{@link Deployment#isNew()} == true，则还会将其持久化
   * @param deployment 待部署对象
   */
  public void deploy(DeploymentEntity deployment) {
    bpmnDeployer.deploy(deployment);
  }

  /**
   * 解析ProcessDefinition对象，并获取解析后的ProcessDefinitionCacheEntry。该方法会缓存结果
   * @param processDefinition 流程定义对象
   * @return ProcessDefinition对应的ProcessDefinitionCacheEntry对象
   * @throws NodeFlowException 找不到processDefinitionId对应的ProcessDefinition对象
   */
  public ProcessDefinitionCacheEntry resolveProcessDefinition(ProcessDefinition processDefinition) {
    String deploymentId = processDefinition.getDeploymentId();
    String processDefinitionId = processDefinition.getId();
    DeploymentEntity deploymentEntity = deploymentEntityManager.selectById(deploymentId);
    // find in cache
    ProcessDefinitionCacheEntry processDefinitionCacheEntry = deployCacheManager.findInCache(processDefinitionId);

    if (processDefinitionCacheEntry == null) {
      // try to parse and set into cache, but not persist
      // 为什么这里是尝试parse而不是直接到数据库中搜索？
      // 因为我们需要的不仅仅是数据库中存储的ProcessDefinition等定义，还需要根据Resource解析得到的BpmnModel对象
      deploymentEntity.setNew(false);
      deploy(deploymentEntity);
      // find again
      processDefinitionCacheEntry = deployCacheManager.findInCache(processDefinitionId);
      if (processDefinitionCacheEntry == null) {
        throw new NodeFlowException("找不到id为" + processDefinitionId + "的ProcessDefinition");
      }
    }
    return processDefinitionCacheEntry;
  }

  public ProcessDefinition findProcessDefinitionById(String processDefinitionId) {

    ProcessDefinitionCacheEntry processDefinitionCacheEntry = deployCacheManager.findInCache(processDefinitionId);
    if (processDefinitionCacheEntry != null) {
      return processDefinitionCacheEntry.getProcessDefinition();
    }
    return processDefinitionEntityManager.selectById(processDefinitionId);
  }

  public ProcessDefinition findProcessDefinitionByKey(String processDefinitionKey) {
    return processDefinitionEntityManager.selectLatestProcessDefinitionByKey(processDefinitionKey);
  }

  public void removeDeployment(String deploymentId) {
    // 1. find Deployment
    DeploymentEntity deploymentEntity = deploymentEntityManager.selectById(deploymentId);
    if (deploymentEntity == null) {
      return;
    }
    // 2. find relative ProcessDefinitions and delete
    List<ProcessDefinitionEntity> processDefinitionEntities = processDefinitionEntityManager.selectLatestProcessDefinitionByDeploymentId(deploymentId);
    for (ProcessDefinitionEntity processDefinition : processDefinitionEntities) {
      processDefinitionEntityManager.delete(processDefinition);
      // remove cache
      deployCacheManager.removeFromCache(processDefinition.getId());
    }
    // 3. find relative Resources and delete
    for (ResourceEntity resource : resourceEntityManager.selectResourcesByDeploymentId(
        deploymentId)) {
      resourceEntityManager.delete(resource);
    }
    // TODO: 4. find relative Executions and delete
    for (ProcessDefinitionEntity processDefinition : processDefinitionEntities) {
      List<ExecutionEntity> executionEntities = executionEntityManager.selectExecutionByProcessDefinitionId(
          processDefinition.getId());
      for (ExecutionEntity execution : executionEntities) {
        executionEntityManager.deleteExecution(execution);  // delete Execution and Task and so on
      }
    }
    // 5. delete Deployment
    deploymentEntityManager.delete(deploymentEntity);
  }

  public ProcessEngineConfiguration getProcessEngineConfiguration() {
    return processEngineConfiguration;
  }

  public DeploymentEntityManager getDeploymentEntityManager() {
    return deploymentEntityManager;
  }

  public ProcessDefinitionEntityManager getProcessDefinitionEntityManager() {
    return processDefinitionEntityManager;
  }

  public BpmnDeployer getBpmnDeployer() {
    return bpmnDeployer;
  }

}
